/**
 * suggest 1.0.0
 *
 * $Author: baboo<istxwang@gmail.com>$
 *
 * $Date:2008-11-20 14:41$
 */
(function(){
    if (!String.prototype.trim) {
        String.prototype.trim = function() {
            return this.replace(/^[\s　]+|[\s　]+$/g, '');
        }
    }

    if (!Function.prototype.bind) {
        Function.prototype.bind = function() {
            var _t = this, args = Array.prototype.slice.apply(arguments, [0]), o = args.shift();
    		return function () {
	    		return _t.apply(o, args.concat(Array.prototype.slice.apply(arguments, [0])));	
		    };
        }
    }

    //json callback
    function $json(url, callback) {
		if (callback) url = url + (url.indexOf('?') == -1 ? '?' : '&') + 'callback=' + callback;
		var head = document.getElementsByTagName('head') [0];
		var script = document.createElement('script');
		script.src = url;
		head.appendChild(script);        
    }

    //bind event
    function $bind(element, name, observer) {
        if (element.addEventListener) {
            element.addEventListener(name, observer, false);
        } else if (element.attachEvent) {
            element.attachEvent('on' + name, observer);
        }
    }

    //unbind event
    function $unbind(element, name, observer) {
        if (element.removeEventListener) {
            element.removeEventListener(name, observer, false);
        } else if (element.detachEvent) {
            try {
                element.detachEvent('on' + name, observer);
            } catch (e) {}
        }
    }

    //getElementsByClassName
    function $$(e, c) {
        var children = e.getElementsByTagName('*');
        var elements = [], reg = new RegExp('(^|\\s)' + c + '(\\s|$)');
        for (var i = 0, n = children.length; i < n; i++) {
            var child = children[i];
            if (child.className.match(reg)) elements.push(child);
        }
        return elements;
    }

    //empty function
    function $empty(){}

    window.__BSuggest = function (sets) {
        var defaultSets = {
            valueCheckDelay : 0.2,
            resultHideDelay : 0.5,
            valueField : 0,
            liClass : 'suggest',
            liSelectedClass : 'suggest-selected',
            closeBtnClass : 'suggest-close',
            jsonCallbackName : 'thanks',
            getRetArr : function (j) { return j && j.result },
            getRetWord : function (j) { return j && j.word }
        };
        for (var pro in sets) {
            if (sets.hasOwnProperty(pro)) {
                defaultSets[pro] = sets[pro];
            }
        }
        sets = defaultSets;
        var inpute = sets.inputElement, resulte = sets.resultElement, turnDisable = false, _t = this,
        lastChoosed = lastShowValue = lastValue = oriValue = checkTimer = hideTimer = null,
        $clear = function () { if (checkTimer) clearTimeout(checkTimer); checkTimer = null},
        $v = function() { return inpute.value.trim().toLowerCase() },
    $isHide = function(){return resulte.style.display == 'none'},
    $clearHide = function(){if (hideTimer) clearTimeout(hideTimer); hideTimer = null},
    $setHide = function () { if($isHide()) return; $clearHide(); hideTimer = setTimeout(hide, sets.resultHideDelay * 1000); };
        
        //Opera keyup 事件不能捕捉到DOWN键，所以Opera中绑定了keyup和keydown两个事件
        var _onkeydownorup = function (event) {
            var code = event.keyCode || event.which;
            if (event.type == 'keydown') {
                //KEY:DOWN  for opera
                if (code == 40) {
                    chooseNext();
                }
                return;
            }
            switch (code) {
                //KEY:DOWN
                case 40:
                if (!window.opera) chooseNext();
                return;
                //KEY:UP
                case 38:
                return chooseNext(true);
                //KEY:ENTER
                case 13:
                hide();
                return (sets.onPressEnter || $empty)();
                //KEY:ESC
                case 27:
                return hide();
                default:
                checkValue();
            }
        };

        //input element on blur event handler
        var _onblur = function () { hide(); $clear();__BSuggest.Mgr.activeInstance = null;};

        var checkValue = function () {
            var v = $v();
            if (v == '') turnDisable = false;
            if (lastShowValue == v && !$isHide()) return;
            lastValue = v;
            $setHide();
            if (turnDisable) return;
            if (lastValue == '' || __BSuggest.NoResultPrefix.has(lastValue)) {
                $clearHide();
                hide();
                return;
            }
            __BSuggest.Mgr.activeInstance = _t;
            if (!sets.valueCheckDelay) return show(lastValue);
            $clear();
            checkTimer = setTimeout(delayShow.bind(null, lastValue), sets.valueCheckDelay * 1000);
        };

        var delayShow = function (w) {
            if (w == $v()) show(w);
        }

        var show = function (w) {
            var url = sets.urlTpl.replace(/\{0\}/, encodeURIComponent(w));
            if (!window[sets.jsonCallbackName]) {
                window[sets.jsonCallbackName] = function (j) {
                    realShow(j);
                }
            }
            new $json(url, sets.jsonCallbackName);
        };
        
        var bindli = function (li, title) {
            li.title = title;
            li.onmouseover = function () { choose(li, true) };
            li.onmousedown = function () { check(li.title) };
        };

        var removeScriptElement = function (url) {
            if (sets.jsonCallbackName) 
                url = url + (url.indexOf('?') == -1 ? '?' : '&') + 'callback=' + sets.jsonCallbackName;
            try {
                var head = document.getElementsByTagName('head') [0];
                var scripts = head.getElementsByTagName('script');
                for (var i = 0; i < scripts.length; i++) {
                    if (scripts[i].src == url) {
                        head.removeChild(scripts[i]);
                    }
                }
            } catch (e) {}
        };

        var realShow = function (o) {
            var w = sets.getRetWord(o);
            var ret = sets.getRetArr(o);
            var url = sets.urlTpl.replace(/\{0\}/, encodeURIComponent(w));
            if (!w || w != $v() || !ret || !ret.length) {
                if (ret && ret.length == 0) {
                    __BSuggest.NoResultPrefix.add(w);
                }
                setTimeout(function(){removeScriptElement(url)}, 50);
                return;
            }
            $clearHide();
            lastShowValue = w;
            lastChoosed = null;
            var sb = [];
            if (sets.prefix) sb.push(sets.prefix);
            for (var i = 0, n = ret.length; i < n; i++) {
                sb.push(sets.process(ret[i], i));
            }
            if (sets.suffix) sb.push(sets.suffix);
            resulte.innerHTML = sb.join('');
            var lis = $$(resulte, sets.liClass), li;
            for (var i = 0, n = lis.length; i < n; i++) {
                li = lis[i];
                bindli(li, ret[i][sets.valueField]);
            }
            var close_btn = $$(resulte, sets.closeBtnClass);
            if (close_btn.length != 0) {
                close_btn[0].onmousedown = close;
            }
            resulte.style.display = '';
            setTimeout(function(){removeScriptElement(url)}, 50);
        };

        var hide = function () {
            if ($isHide()) return;
            resulte.style.display = 'none';
            (sets.onHide || $empty)();
            lastChoosed = null;
        };

        var setFocus = function () {
            inpute.focus();
        };

        var close = function() {
            hide();
            turnDisable = true;
            setTimeout(setFocus, 100);
        };

        var check = function (w) {
            lastValue = w;
            inpute.value = w;
            hide();
            (sets.onCheck || $empty)(inpute, w);
        };
        
        var unchoose = function (o) {
            var reg = new RegExp('(^|\\s+)(' + sets.liSelectedClass + ')($|\\s+)');
            o.className = o.className.replace(reg, '$1$3');
        };

        var choose = function (o, doNotChangeValue) {
            if (lastChoosed) unchoose(lastChoosed);
            if (o === null) {
                lastChoosed = null;
                inpute.value = oriValue || '';
                return;
            }
            var reg = new RegExp('(^|\\s+)(' + sets.liSelectedClass + ')($|\\s+)');
            if (!o.className.match(reg)) {
                o.className = o.className + ' ' + sets.liSelectedClass;
            }
            lastChoosed = o;
            if (!doNotChangeValue) {
                var w = o.title;
                lastValue = w.toLowerCase();
                inpute.value = w;
            }
        };

        var chooseNext = function (up) {
            if ($isHide()) return;
            var lis = $$(resulte, sets.liClass);
            if (lis.length == 0) return;
            if (lastChoosed == null) {
                oriValue = inpute.value;
                if (up) return choose(lis[lis.length - 1]);
                return choose(lis[0]);
            }

            var i;
            for (i = 0; i < lis.length; i++) {
                if (lastChoosed == lis[i]) break;
            }
            if (up) {
                if (i == 0) return choose(null);
                return choose(lis[--i]);
            }
            if (i == lis.length - 1) return choose(null);
            return choose(lis[++i]);
        };

        //public function
        var activated = false;
        this.activate = function () {
            if (activated) return;
            if (window.opera) {
                $bind(inpute, 'keydown', _onkeydownorup);
            }
            $bind(inpute, 'keyup', _onkeydownorup);
            $bind(inpute, 'blur', _onblur);
            activated = true;
        };

        this.disable = function () {
            if (!activated) return;
            if (window.opera) {
                $unbind(inpute, 'keydown', _onkeydownorup);
            }
            $unbind(inpute, 'keyup', _onkeydownorup);
            $unbind(inpute, 'blur', _onblur);
            activated = false;
        };

        this.setUrlTpl = function (newUrl) {
            sets.urlTpl = newUrl;
            lastShowValue = null;
            __BSuggest.NoResultPrefix.length = 0;
        };
        
        this.choose = choose;

        this.close = close;

        this.activate();
        
        this.__show = realShow;
    };
    __BSuggest.NoResultPrefix = {
        list: [],
        add : function (w) { this.list.push(w) },
        has : function (w) {
            if (!w) return false;
            for (var i = 0, n = this.list.length; i < n; i ++) {
                if (w.indexOf(this.list[i]) == 0) return true;
            }
            return false;
        }
    };
    __BSuggest.Mgr = {
        activeInstance : null,
        show: function (j) {
            if (this.activeInstance) this.activeInstance.__show(j);
        }
    };
})();
